home *** CD-ROM | disk | FTP | other *** search
/ Palm Utilities / Palm_Utilities_CD-ROM_2001_2001.iso / files / pim / Hot Date 1.3e / hotdate.exe / hotdate / datebook.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-05-22  |  52.3 KB  |  1,479 lines

  1. /* $Id: datebook.c,v 1.47 2000/05/22 17:17:28 chrisf Exp $ */
  2.  
  3. /*
  4. Hot Date - A DatebookDB displayer for the PalmPilot
  5. Copyright (C) 1999 Chris Faherty
  6.  
  7. This program is free software; you can redistribute it and/or
  8. modify it under the terms of the GNU General Public License
  9. as published by the Free Software Foundation; either version 2
  10. of the License, or (at your option) any later version.
  11.  
  12. This program is distributed in the hope that it will be useful,
  13. but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. GNU General Public License for more details.
  16.  
  17. You should have received a copy of the GNU General Public License
  18. along with this program; if not, write to the Free Software
  19. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  20. */
  21.  
  22. /*
  23.  * Just so you know these routines are pulled from the Datebook source code
  24.  * as provided by Palm.  At least that is what I started from, I may have
  25.  * touched a few lines to get it to compile with gcc without warnings.
  26.  *
  27.  * They are used to get a list of appointments for a particular day or a
  28.  * range of days.  It is fairly sophisticated to compute this stuff so I
  29.  * didn't bother and just skarfed their code.
  30.  */
  31.  
  32. #include <Pilot.h>
  33. #include "callback.h"
  34. #include "hotdate.h"
  35. #include "datebook.h"
  36.  
  37. extern struct sPrefsR *PrefsR;
  38. extern UInt apptAlarmLine;
  39.  
  40. /*
  41.  * Static function prototypes
  42.  */
  43. static void ApptUnpack(ApptPackedDBRecordPtr src, ApptDBRecordPtr dest);
  44. static Int ApptListCompare(ApptInfoPtr a1, ApptInfoPtr a2, Long extra);
  45. static Boolean NextRepeat(ApptDBRecordPtr apptRec, DatePtr dateP);
  46. static Boolean IsException(ApptDBRecordPtr apptRec, DateType date);
  47. static Boolean Datebk3Done(ApptPackedDBRecordPtr r);
  48.  
  49. /***********************************************************************
  50.  *
  51.  * FUNCTION:    DateCompare
  52.  *
  53.  * DESCRIPTION: This routine compares two dates.
  54.  *
  55.  * PARAMETERS:  d1 - a date 
  56.  *              d2 - a date 
  57.  *
  58.  * RETURNED:    if d1 > d2  returns a positive int
  59.  *              if d1 < d2  returns a negative int
  60.  *              if d1 = d2  returns zero
  61.  *
  62.  * NOTE: This routine treats the DateType structure like an unsigned int,
  63.  *       it depends on the fact the the members of the structure are ordered
  64.  *       year, month, day form high bit to low low bit.
  65.  *
  66.  * REVISION HISTORY:
  67.  *            Name    Date        Description
  68.  *            ----    ----        -----------
  69.  *            art    6/12/95        Initial Revision
  70.  *
  71.  ***********************************************************************/
  72. Int DateCompare(DateType d1, DateType d2)
  73. {
  74.     UInt int1, int2;
  75.     
  76.     int1 = DateToInt(d1);
  77.     int2 = DateToInt(d2);
  78.     
  79.     if (int1 > int2) return (1);
  80.     else if (int1 < int2) return (-1);
  81.     return 0;
  82. }
  83.  
  84. /***********************************************************************
  85.  *
  86.  * FUNCTION:    TimeCompare
  87.  *
  88.  * DESCRIPTION: This routine compares two times.  "No time" is represented
  89.  *              by minus one, and is considered less than all times.
  90.  *
  91.  * PARAMETERS:  nothing
  92.  *
  93.  * RETURNED:    if t1 > t2  returns a positive int
  94.  *              if t1 < t2  returns a negative int
  95.  *              if t1 = t2  returns zero
  96.  *
  97.  * REVISION HISTORY:
  98.  *            Name    Date        Description
  99.  *            ----    ----        -----------
  100.  *            art    6/12/95        Initial Revision
  101.  *
  102.  ***********************************************************************/
  103. Int TimeCompare(TimeType t1, TimeType t2)
  104. {
  105.     Int int1, int2;
  106.     
  107.     int1 = TimeToInt(t1);
  108.     int2 = TimeToInt(t2);
  109.     
  110.     if (int1 > int2) return (1);
  111.     else if (int1 < int2) return (-1);
  112.     return 0;
  113. }
  114.  
  115. /************************************************************
  116.  *
  117.  *  FUNCTION: ApptUnpack
  118.  *
  119.  *  DESCRIPTION: Fills in the ApptDBRecord structure
  120.  *
  121.  *  PARAMETERS: database record
  122.  *
  123.  *  RETURNS: the record unpacked
  124.  *
  125.  *  CREATED: 1/25/95 
  126.  *
  127.  *  BY: Roger Flores
  128.  *
  129.  *************************************************************/
  130. static void ApptUnpack(ApptPackedDBRecordPtr src, ApptDBRecordPtr dest)
  131. {
  132.     ApptDBRecordFlags flags;
  133.     char *p;
  134.     
  135.     flags = src->flags;
  136.     p = &src->firstField;
  137.  
  138.     dest->when = (ApptDateTimeType *) src;
  139.     
  140.     if (flags.alarm) {
  141.         dest->alarm = (AlarmInfoType *) p;
  142.         p += sizeof(AlarmInfoType);
  143.     } else dest->alarm = NULL;
  144.     
  145.     if (flags.repeat) {
  146.         dest->repeat = (RepeatInfoType *) p;
  147.         p += sizeof(RepeatInfoType);
  148.     } else dest->repeat = NULL;
  149.     
  150.     if (flags.exceptions) {
  151.         dest->exceptions = (ExceptionsListType *) p;
  152.         p += sizeof(UInt) + 
  153.             (((ExceptionsListType *) p)->numExceptions * sizeof(DateType));
  154.     } else dest->exceptions = NULL;
  155.     
  156.     if (flags.description) {
  157.         dest->description = p;
  158.         p += StrLen(p) + 1;
  159.     } else dest->description = NULL;
  160.     
  161.     if (flags.note) {
  162.         dest->note = p;
  163.     } else dest->note = NULL;
  164. }
  165.  
  166. /***********************************************************************
  167.  *
  168.  * FUNCTION:    ApptRepeatsOnDate
  169.  *
  170.  * DESCRIPTION: This routine returns true if a repeating appointment
  171.  *              occurrs on the specified date.
  172.  *
  173.  * PARAMETERS:  apptRec - a pointer to an appointment record
  174.  *              date    - date to check              
  175.  *
  176.  * RETURNED:    true if the appointment occurs on the date specified
  177.  *
  178.  * REVISION HISTORY:
  179.  *            Name    Date        Description
  180.  *            ----    ----        -----------
  181.  *            art    6/14/95        Initial Revision
  182.  *
  183.  ***********************************************************************/
  184. Boolean ApptRepeatsOnDate(ApptDBRecordPtr apptRec, DateType date)
  185. {
  186.     Int  i;
  187.     Word freq;
  188.     Word weeksDiff;
  189.     Word dayInMonth;
  190.     Word dayOfWeek;
  191.     Word dayOfMonth;
  192.     Word firstDayOfWeek;
  193.     long dateInDays;
  194.     long startInDays;
  195.     Boolean onDate;
  196.     DatePtr exceptions;
  197.     DateType startDate;
  198.  
  199.     /* Is the date passed before the start date of the appointment? */
  200.     if (DateCompare (date, apptRec->when->date) < 0) return (false);
  201.  
  202.     /* Is the date passed after the end date of the appointment? */
  203.     if (DateCompare (date, apptRec->repeat->repeatEndDate) > 0) return (false);
  204.  
  205.     /*
  206.      * Get the frequency of occurrecne (ex: every 2nd day,
  207.      * every 3rd month, etc.).
  208.      */
  209.     freq = apptRec->repeat->repeatFrequency;
  210.     
  211.     /* Get the date of the first occurrecne of the appointment. */
  212.     startDate = apptRec->when->date;
  213.  
  214.     switch (apptRec->repeat->repeatType) {
  215.     /* Daily repeating appointment. */
  216.     case repeatDaily:
  217.         dateInDays = DateToDays(date);
  218.         startInDays = DateToDays(startDate);
  219.         onDate = ((dateInDays - startInDays) % freq) == 0;
  220.         break;
  221.             
  222.     /*
  223.      * Weekly repeating appointment (ex: every Monday and Friday). 
  224.      * Yes, weekly repeating appointment can occur more then once a
  225.      * week.
  226.      */
  227.     case repeatWeekly:
  228.         /* Are we on a day of the week that the appointment repeats on. */
  229.         dayOfWeek = DayOfWeek(date.month, date.day, date.year+firstYear);
  230.         onDate = ((1 << dayOfWeek) & apptRec->repeat->repeatOn);
  231.         if (! onDate) break;
  232.  
  233.         /*
  234.          * Are we in a week in which the appointment occurrs, if not 
  235.          * move to that start of the next week in which the appointment
  236.          * does occur.
  237.          */
  238.         dateInDays = DateToDays(date);
  239.         startInDays = DateToDays(startDate);
  240.  
  241.         firstDayOfWeek = (DayOfWeek(1, 1, firstYear) - 
  242.             apptRec->repeat->repeatStartOfWeek + daysInWeek) % daysInWeek;
  243.  
  244.         weeksDiff = (((dateInDays + firstDayOfWeek) / daysInWeek) - 
  245.                          ((startInDays + firstDayOfWeek) / daysInWeek)) %freq;
  246.         onDate = (weeksDiff == 0);
  247.         break;
  248.  
  249.     /*
  250.      * Monthly-by-day repeating appointment (ex: the 3rd Friday of every
  251.      * month).
  252.      */
  253.     case repeatMonthlyByDay:
  254.         /* Are we in a month in which the appointment repeats. */
  255.         onDate = ((((date.year - startDate.year) * monthsInYear) + 
  256.                        (date.month - startDate.month)) % freq) == 0;
  257.         if (! onDate) break;
  258.  
  259.         /* Do the days of the month match (ex: 3rd Friday) */
  260.         dayOfMonth = DayOfMonth(date.month, date.day, date.year+firstYear);
  261.         onDate = (dayOfMonth == apptRec->repeat->repeatOn);
  262.         if (onDate) break;
  263.  
  264.         /*
  265.          * If the appointment repeats on one of the last days of the month,
  266.          * check if the date passed is also one of the last days of the 
  267.          * month.  By last days of the month we mean: last sunday, 
  268.          * last monday, etc.
  269.          */
  270.         if ((apptRec->repeat->repeatOn >= domLastSun) &&
  271.              (dayOfMonth >= dom4thSun)) {
  272.             dayOfWeek = DayOfWeek(date.month, date.day, date.year+firstYear);
  273.             dayInMonth = DaysInMonth(date.month, date.year+firstYear);
  274.             onDate = (((date.day + daysInWeek) > dayInMonth) &&
  275.                          (dayOfWeek == (apptRec->repeat->repeatOn % daysInWeek)));
  276.         }
  277.         break;                        
  278.  
  279.     /*
  280.      * Monthly-by-date repeating appointment (ex: the 15th of every
  281.      * month).
  282.      */
  283.     case repeatMonthlyByDate:
  284.         /* Are we in a month in which the appointment repeats. */
  285.         onDate = ((((date.year - startDate.year) * monthsInYear) + 
  286.                        (date.month - startDate.month)) % freq) == 0;
  287.         if (! onDate) break;
  288.  
  289.         /* Are we on the same day of the month as the start date. */
  290.         onDate = (date.day == startDate.day);
  291.         if (onDate) break;
  292.  
  293.         /*
  294.          * If the staring day of the appointment is greater then the 
  295.          * number of day in the month passed, and the day passed is the 
  296.          * last day of the month, then the appointment repeats on the day.
  297.          */
  298.         dayInMonth = DaysInMonth(date.month, date.year+firstYear);
  299.         onDate = ((startDate.day > dayInMonth) && (date.day == dayInMonth));
  300.         break;
  301.  
  302.     /* Yearly repeating appointment. */
  303.     case repeatYearly:
  304.         /* Are we in a year in which the appointment repeats. */
  305.         onDate = ((date.year - startDate.year) % freq) == 0;
  306.         if (! onDate) break;
  307.  
  308.         /* Are we on the month and day that the appointment repeats. */
  309.         onDate = (date.month == startDate.month) &&
  310.                   (date.day == startDate.day);
  311.         if (onDate) break;
  312.  
  313.         /* Specal leap day processing. */
  314.         if ((startDate.month == february) && 
  315.               (startDate.day == 29) &&
  316.               (date.month == february) && 
  317.               (date.day == DaysInMonth(date.month, date.year+firstYear))) {
  318.             onDate = true;
  319.         }                      
  320.         break;
  321.  
  322.     /* this is to get rid of the compiler warning */
  323.     default:
  324.         onDate = false;
  325.         break;
  326.     }
  327.  
  328.     /* Check for an exception. */
  329.     if ((onDate) && (apptRec->exceptions)) {
  330.         exceptions = &apptRec->exceptions->exception;
  331.         for (i=0; i < apptRec->exceptions->numExceptions; i++) {
  332.             if (DateCompare(date, exceptions[i]) == 0) {
  333.                 onDate = false;
  334.                 break;
  335.             }
  336.         }
  337.     }
  338.  
  339.     return (onDate);
  340. }
  341.  
  342. /***********************************************************************
  343.  *
  344.  * FUNCTION:    ApptFindFirst
  345.  *
  346.  * DESCRIPTION: This routine finds the first appointment on the specified
  347.  *              day.
  348.  *
  349.  * PARAMETERS:  dbP    - pointer to the database
  350.  *              date   - date to search for
  351.  *              indexP - pointer to the index of the first record on the 
  352.  *                       specified day (returned value)
  353.  *
  354.  * RETURNED:    true if a record has found
  355.  *
  356.  * REVISION HISTORY:
  357.  *            Name    Date        Description
  358.  *            ----    ----        -----------
  359.  *            art    6/15/95        Initial Revision
  360.  *
  361.  ***********************************************************************/
  362. Boolean ApptFindFirst(DmOpenRef dbP, DateType date, UIntPtr indexP)
  363. {
  364.     Err err;
  365.     Int numOfRecords;
  366.     Int kmin, probe, i;     /* all positions in the database. */
  367.     Int result = 0;         /* result of comparing two records */
  368.     UInt index;
  369.     VoidHand recordH;
  370.     Boolean found = false;
  371.     ApptPackedDBRecordPtr r;
  372.  
  373.     kmin = probe = 0;
  374.     numOfRecords = DmNumRecords(dbP);
  375.     
  376.     while (numOfRecords > 0) {
  377.         i = numOfRecords >> 1;
  378.         probe = kmin + i;
  379.         
  380.         index = probe;
  381.         recordH = DmQueryNextInCategory(dbP, &index, dmAllCategories);
  382.         if (recordH) {
  383.             r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
  384.             if (r->flags.repeat) result = 1;
  385.             else result = DateCompare(date, r->when.date);
  386.             MemHandleUnlock(recordH);
  387.         }
  388.         /*
  389.          * If no handle, assume the record is deleted, deleted records
  390.          * are greater.
  391.          */
  392.         else result = -1;
  393.  
  394.         /* If the date passed is less than the probe's date, keep searching. */
  395.         if (result < 0) numOfRecords = i;
  396.  
  397.         /*
  398.          * If the date passed is greater than the probe's date, keep searching.
  399.          */
  400.         else if (result > 0) {
  401.             kmin = probe + 1;
  402.             numOfRecords = numOfRecords - i - 1;
  403.         }
  404.  
  405.         /* If the records are equal find the first record on the day. */
  406.         else {
  407.             found = true;
  408.             *indexP = index;
  409.             while (true) {
  410.                 err = DmSeekRecordInCategory(dbP, &index, 1, dmSeekBackward, 
  411.                     dmAllCategories);
  412.                 if (err == dmErrSeekFailed) break;
  413.                 
  414.                 recordH = DmQueryRecord(dbP, index);
  415.                 r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
  416.                 if (r->flags.repeat) result = 1;
  417.                 else result = DateCompare(date, r->when.date);
  418.                 MemHandleUnlock(recordH);
  419.                 if (result != 0) break;
  420.                 *indexP = index;
  421.             }
  422.  
  423.             break;
  424.         }
  425.     }
  426.     
  427.     /*
  428.      * If that were no appointments on the specified day, return the 
  429.      * index of the next appointment (on a future day).
  430.      */
  431.     if (! found) {
  432.         if (result < 0) *indexP = probe;
  433.         else *indexP = probe + 1;
  434.     }
  435.  
  436.     return (found);
  437. }
  438.  
  439. /***********************************************************************
  440.  *
  441.  * FUNCTION:    ApptListCompare
  442.  *
  443.  * DESCRIPTION: This routine compares two entries in the appointment list, 
  444.  *              it's called by ApptGetAppointments via the quick sort 
  445.  *              routine.
  446.  *
  447.  * PARAMETERS:  a     - a pointer to an entry in the appointment list
  448.  *              b     - a pointer to an entry in the appointment list
  449.  *              extra - extra data passed to quick sort - not used
  450.  *
  451.  * RETURNED:    if a1 > a2  returns a positive int
  452.  *              if a1 < a2  returns a negative int
  453.  *              if a1 = a2  returns zero
  454.  *
  455.  * REVISION HISTORY:
  456.  *            Name    Date        Description
  457.  *            ----    ----        -----------
  458.  *            art    6/15/95        Initial Revision
  459.  *
  460.  ***********************************************************************/
  461. static Int ApptListCompare(ApptInfoPtr a1, ApptInfoPtr a2, Long extra)
  462. {
  463.     Int result;
  464.     
  465.     CALLBACK_PROLOGUE
  466.  
  467.     result = DateCompare(a1->date, a2->date);
  468.     if (result == 0) {
  469.         result = TimeCompare(a1->startTime, a2->startTime);
  470.         if (result == 0) {
  471.             result = TimeCompare(a1->endTime, a2->endTime);
  472.         }
  473.     }
  474.  
  475.     CALLBACK_EPILOGUE
  476.  
  477.     return result;
  478. }
  479.  
  480. /***********************************************************************
  481.  *
  482.  * FUNCTION:    ApptNextRepeat
  483.  *
  484.  * DESCRIPTION: This routine computes the date of the next 
  485.  *              occurrence of a repeating appointment.
  486.  *
  487.  * PARAMETERS:  apptRec - a pointer to an appointment record
  488.  *              date    - passed:   date to start from
  489.  *                        returned: date of next occurrence             
  490.  *
  491.  * RETURNED:    true if the appointment occurs again
  492.  *
  493.  * REVISION HISTORY:
  494.  *            Name    Date        Description
  495.  *            ----    ----        -----------
  496.  *            art    6/14/95    Initial Revision
  497.  *
  498.  ***********************************************************************/
  499. static Boolean NextRepeat(ApptDBRecordPtr apptRec, DatePtr dateP)
  500. {
  501.     Int  i;
  502.     Word day;
  503.     Word freq;
  504.     Word year;
  505.     Word adjust;
  506.     Word weeksDiff;
  507.     Word monthsDiff;
  508.     Word daysInMonth;
  509.     Word dayOfWeek;
  510.     Word apptWeekDay;
  511.     Word firstDayOfWeek;
  512.     Word daysTilNext;
  513.     Word monthsTilNext;
  514.     ULong dateInDays;
  515.     ULong startInDays;
  516.     DateType start;
  517.     DateType date;
  518.     DateType next;
  519.  
  520.     date = *dateP;
  521.  
  522.     /* Is the date passed after the end date of the appointment? */
  523.     if (DateCompare(date, apptRec->repeat->repeatEndDate) > 0)
  524.         return (false);
  525.     
  526.     /* Is the date passed before the start date of the appointment? */
  527.     if (DateCompare(date, apptRec->when->date) < 0)
  528.         date = apptRec->when->date;
  529.  
  530.     /*
  531.      * Get the frequency on occurrecne (ex: every 2nd day,
  532.      * every 3rd month, etc).
  533.      */
  534.     freq = apptRec->repeat->repeatFrequency;
  535.     
  536.     /* Get the date of the first occurrecne of the appointment. */
  537.     start = apptRec->when->date;
  538.  
  539.     switch (apptRec->repeat->repeatType) {
  540.     /* Daily repeating appointment. */
  541.     case repeatDaily:
  542.         dateInDays = DateToDays(date);
  543.         startInDays = DateToDays(start);
  544.         daysTilNext = (dateInDays - startInDays + freq - 1) / freq * freq;
  545.         if (startInDays + daysTilNext > (ULong) maxDays) return (false);
  546.         DateDaysToDate(startInDays + daysTilNext, &next);
  547.         break;
  548.  
  549.     /*
  550.      * Weekly repeating appointment (ex: every Monday and Friday). 
  551.      * Yes, weekly repeating appointment can occur more then once a
  552.      * week.
  553.      */
  554.     case repeatWeekly:
  555.         dateInDays = DateToDays(date);
  556.         startInDays = DateToDays(start);
  557.  
  558.         firstDayOfWeek = (DayOfWeek (1, 1, firstYear) - 
  559.             apptRec->repeat->repeatStartOfWeek + daysInWeek) % daysInWeek;
  560.  
  561.         dayOfWeek = DayOfWeek (date.month, date.day, date.year+firstYear);
  562.         apptWeekDay = (dayOfWeek - apptRec->repeat->repeatStartOfWeek +
  563.             daysInWeek) % daysInWeek;
  564.  
  565.         /*
  566.          * Are we in a week in which the appointment occurrs, if not 
  567.          * move to that start of the next week in which the appointment
  568.          * does occur.
  569.          */
  570.         weeksDiff = (((dateInDays + firstDayOfWeek) / daysInWeek) - 
  571.                          ((startInDays + firstDayOfWeek) / daysInWeek)) %freq;
  572.         if (weeksDiff) {
  573.             adjust = ((freq - weeksDiff) * daysInWeek)- apptWeekDay;
  574.             apptWeekDay = 0;
  575.             dayOfWeek = (dayOfWeek + adjust) % daysInWeek;
  576.         } else adjust = 0;
  577.  
  578.         /* Find the next day on which the appointment repeats. */
  579.         for (i=0; i < daysInWeek; i++) {
  580.             if (apptRec->repeat->repeatOn & (1 << dayOfWeek)) break;
  581.             adjust++;
  582.             if (++dayOfWeek == daysInWeek) dayOfWeek = 0;
  583.             if (++apptWeekDay == daysInWeek) adjust += (freq - 1) * daysInWeek;
  584.         }
  585.  
  586.         if (dateInDays + adjust > (ULong) maxDays) return (false);
  587.         DateDaysToDate(dateInDays + adjust, &next);
  588.         break;
  589.  
  590.     /*
  591.      * Monthly-by-day repeating appointment (ex: the 3rd Friday of every
  592.      * month).
  593.      */
  594.     case repeatMonthlyByDay:
  595.         /* Compute the number of month until the appointment repeats again. */
  596.         monthsTilNext = (date.month - start.month);
  597.         monthsTilNext = ((((date.year - start.year) * monthsInYear) + 
  598.             (date.month - start.month)) + freq - 1) / freq * freq;
  599.  
  600.         while (true) {
  601.             year = start.year + 
  602.                              (start.month - 1 + monthsTilNext) / monthsInYear;
  603.             if (year >= numberOfYears) return (false);
  604.  
  605.             next.year = year;
  606.             next.month = (start.month - 1 + monthsTilNext) % monthsInYear + 1;
  607.  
  608.             dayOfWeek = DayOfWeek(next.month, 1, next.year+firstYear);
  609.             if ((apptRec->repeat->repeatOn % daysInWeek) >= dayOfWeek)
  610.                 day = apptRec->repeat->repeatOn - dayOfWeek + 1;
  611.             else
  612.                 day = apptRec->repeat->repeatOn + daysInWeek - dayOfWeek + 1;
  613.  
  614.             /*
  615.              * If repeat-on day is between the last sunday and the last
  616.              * saturday, make sure we're not passed the end of the month.
  617.              */
  618.             if ((apptRec->repeat->repeatOn >= domLastSun) &&
  619.                   (day > DaysInMonth (next.month, next.year+firstYear))) {
  620.                 day -= daysInWeek;
  621.             }
  622.             next.day = day;
  623.  
  624.             /*
  625.              * Its posible that "next date" calculated above is 
  626.              * before the date passed.  If so, move forward
  627.              * by the length of the repeat freguency and preform
  628.              * the calculation again.
  629.              */
  630.             if (DateToInt(date) > DateToInt (next)) monthsTilNext += freq;
  631.             else break;
  632.         }
  633.         break;                        
  634.  
  635.     /*
  636.      * Monthly-by-date repeating appointment (ex: the 15th of every
  637.      * month).
  638.      */
  639.     case repeatMonthlyByDate:
  640.         /* Compute the number of month until the appointment repeats again. */
  641.         monthsDiff = ((date.year - start.year) * monthsInYear) + 
  642.             (date.month - start.month);
  643.         monthsTilNext = (monthsDiff + freq - 1) / freq * freq;
  644.  
  645.         if ((date.day > start.day) && (!(monthsDiff % freq)))
  646.             monthsTilNext += freq;
  647.  
  648.         year = start.year + 
  649.                          (start.month - 1 + monthsTilNext) / monthsInYear;
  650.         if (year >= numberOfYears) return (false);
  651.  
  652.         next.year = year;
  653.         next.month = (start.month - 1 + monthsTilNext) % monthsInYear + 1;
  654.         next.day = start.day;
  655.  
  656.         /* Make sure we're not passed the last day of the month. */
  657.         daysInMonth = DaysInMonth(next.month, next.year+firstYear);
  658.         if (next.day > daysInMonth) next.day = daysInMonth;
  659.         break;
  660.  
  661.     /* Yearly repeating appointment. */
  662.     case repeatYearly:
  663.         next.day = start.day;
  664.         next.month = start.month;
  665.  
  666.         year = start.year + 
  667.             ((date.year - start.year + freq - 1) / freq * freq);
  668.  
  669.         if ((date.month > start.month) ||
  670.             ((date.month == start.month) && (date.day > start.day)))
  671.              year += freq;
  672.  
  673.         /* Specal leap day processing. */
  674.         if ((next.month == february) && (next.day == 29) &&
  675.               (next.day > DaysInMonth (next.month, year+firstYear))) {
  676.             next.day = DaysInMonth (next.month, year+firstYear);
  677.         }                      
  678.         if (year >= numberOfYears) return (false);
  679.  
  680.         next.year = year;    
  681.         break;
  682.  
  683.     /* this is to get rid of gcc warning */
  684.     default:
  685.         return (false);
  686.     }
  687.  
  688.     /* Is the next occurrence after the end date of the appointment? */
  689.     if (DateCompare(next, apptRec->repeat->repeatEndDate) > 0)
  690.         return (false);
  691.  
  692.     ErrFatalDisplayIf((DateToInt(next) < DateToInt(*dateP)),
  693.         "Calculation error");
  694.  
  695.     *dateP = next;
  696.     return (true);
  697. }
  698.  
  699. /***********************************************************************
  700.  *
  701.  * FUNCTION:    IsException
  702.  *
  703.  * DESCRIPTION: This routine returns true the date passed is in a 
  704.  *              repeating appointment's exception list.
  705.  *
  706.  * PARAMETERS:  apptRec - a pointer to an appointment record
  707.  *              date    - date to check              
  708.  *
  709.  * RETURNED:    true if the date is an exception date.
  710.  *
  711.  * REVISION HISTORY:
  712.  *            Name    Date        Description
  713.  *            ----    ----        -----------
  714.  *            art    6/14/95        Initial Revision
  715.  *
  716.  ***********************************************************************/
  717. static Boolean IsException(ApptDBRecordPtr apptRec, DateType date)
  718. {
  719.     int i;
  720.     DatePtr exceptions;
  721.  
  722.     if (apptRec->exceptions) {
  723.         exceptions = &apptRec->exceptions->exception;
  724.         for (i=0; i < apptRec->exceptions->numExceptions; i++) {
  725.             if (DateCompare(date, exceptions[i]) == 0) return (true);
  726.         }
  727.     }
  728.     return (false);
  729. }
  730.  
  731. /***********************************************************************
  732.  *
  733.  * FUNCTION:    ApptNextRepeat
  734.  *
  735.  * DESCRIPTION: This routine computes the next occurrence of a 
  736.  *              repeating appointment.
  737.  *
  738.  * PARAMETERS:  apptRec - a pointer to an appointment record
  739.  *              dateP   - passed:   date to start from
  740.  *                        returned: date of next occurrence            
  741.  *
  742.  * RETURNED:    true if there is an occurrence of the appointment
  743.  *              between the date passed and the appointment's end date
  744.  *
  745.  * REVISION HISTORY:
  746.  *            Name    Date        Description
  747.  *            ----    ----        -----------
  748.  *            art    6/20/95    Initial Revision
  749.  *
  750.  ***********************************************************************/
  751. Boolean ApptNextRepeat(ApptDBRecordPtr apptRec, DatePtr dateP)
  752. {
  753.     DateType date;
  754.     
  755.     date = *dateP;
  756.     
  757.     while (true) {
  758.         /* Compute the next time the appointment repeats. */
  759.         if (! NextRepeat(apptRec, &date)) return (false);
  760.  
  761.         /* Check if the date computed is in the exceptions list. */
  762.         if (! IsException(apptRec, date)) {
  763.             *dateP = date;
  764.             return (true);
  765.         }
  766.             
  767.         DateAdjust(&date, 1);
  768.     }        
  769. }
  770.  
  771. /***********************************************************************
  772.  *
  773.  * FUNCTION:    AddAppointmentToList
  774.  *
  775.  * DESCRIPTION: This routine adds an appointment to a list of appointments. 
  776.  *
  777.  * PARAMETERS:  dbP    - pointer to the database
  778.  *              date   - date to search for
  779.  *              countP - number of appointments on the specified 
  780.  *                       day (returned value)
  781.  *              type   - 0 for datebook 1 for todo
  782.  *
  783.  * RETURNED:    handle of the appointment list (ApptInfoType)
  784.  *
  785.  * REVISION HISTORY:
  786.  *            Name    Date        Description
  787.  *            ----    ----        -----------
  788.  *            art    4/17/96    Initial Revision
  789.  *
  790.  ***********************************************************************/
  791. Boolean AddAppointmentToList(VoidHand * apptListH, ULong shrunk, UInt count,
  792.     UInt origcount, TimeType startTime, TimeType endTime, DateType date,
  793.     UInt recordNum, Word type)
  794. {
  795.     Err err;
  796.     Word newSize;
  797.     ApptInfoPtr apptList;
  798.  
  799.     if ((count+origcount) >= apptMaxPerDay) return false;
  800.  
  801.     if ((count == 0) && ! *apptListH) {
  802.         /* Allocated a block to hold the appointment list. */
  803.         *apptListH = MemHandleNew(sizeof(ApptInfoType) * (apptMaxPerDay / 10));
  804.         /*
  805.          * This code comes from DateDB.c (Datebook source).  The next two
  806.          * lines are incorrect in that source, but corrected here.  Thanks to
  807.          * Jane B. Halfond.
  808.          */
  809.         ErrFatalDisplayIf(!(*apptListH), "Out of memory");
  810.         if (!(*apptListH)) return (false);
  811.         apptList = MemHandleLock(*apptListH);
  812.     }
  813.         
  814.     /*
  815.      * Resize the list to hold more more appointments.  Keep in mind that in
  816.      * some cases we are appending ApptInfoType structures to memory that
  817.      * has LineItemType structures.  The shrunk parameter is used to skip
  818.      * those LineItemType structures at the beginning.  In other words, the
  819.      * ApptInfoType is used during collection and sorting.. and then they
  820.      * are shrunk into a LineItemType to save memory.
  821.      */
  822.     else if (((count % (apptMaxPerDay / 10)) == 0) ||
  823.         ((MemHandleSize(*apptListH)-shrunk) <
  824.         (sizeof(ApptInfoType)*(((count+(apptMaxPerDay/10))/(apptMaxPerDay/10))
  825.         *(apptMaxPerDay/10))))) {
  826.         if (count + (apptMaxPerDay / 10) > apptMaxPerDay) return (false);
  827.  
  828.         MemHandleUnlock(*apptListH);
  829.         newSize = shrunk+(sizeof(ApptInfoType)*
  830.             ((count+(apptMaxPerDay/10))/
  831.             (apptMaxPerDay/10))*(apptMaxPerDay/10));
  832.         err = MemHandleResize(*apptListH, newSize);
  833.         apptList = MemHandleLock(*apptListH);
  834.         /*
  835.          * Hopefully this will just gracefully abort the operation.
  836.          */
  837.         if (err) return (false);
  838.  
  839.     } else {
  840.         MemHandleUnlock(*apptListH);
  841.         apptList = MemHandleLock(*apptListH);
  842.     }
  843.  
  844.     /*
  845.      * Skip the data that is already shrunk at the beginning.
  846.      */
  847.     apptList = (ApptInfoPtr) (((char *) apptList)+shrunk);
  848.  
  849.     apptList[count].startTime = startTime;
  850.     apptList[count].endTime = endTime;
  851.     apptList[count].recordNum = recordNum;
  852.     apptList[count].date = date;
  853.     apptList[count].type = type;
  854.  
  855.     return (true);
  856. }
  857.  
  858. /*
  859.  * Datebk3 has a "done" checkbox that you can pick for an appointment.  This
  860.  * function tests to see if it is set.
  861.  *
  862.  * http://www.gorilla-haven.org/pimlico/datebk3doc.htm
  863.  */
  864. static Boolean Datebk3Done(ApptPackedDBRecordPtr r)
  865. {
  866.     long l1;
  867.     
  868.     /* Calculate the position of the description */
  869.     l1 = sizeof(ApptDateTimeType)+sizeof(ApptDBRecordFlags);
  870.     if (r->flags.alarm) l1 += sizeof(AlarmInfoType);
  871.     if (r->flags.repeat) l1 += sizeof(RepeatInfoType);
  872.     if (r->flags.exceptions)
  873.         l1 += sizeof(UInt) + 
  874.             (((ExceptionsListType *)
  875.             ((CharPtr) r+l1))->numExceptions*sizeof(DateType));
  876.     if (r->flags.description) l1 += StrLen((CharPtr) r+l1)+1;
  877.  
  878.     /* Check the note and see if it has the done flag */
  879.     if (r->flags.note) {
  880.         if ((StrLen((CharPtr) r+l1) >= 10) &&
  881.             (*((CharPtr) r+l1) == '#') && (*((CharPtr) r+l1+1) == '#') &&
  882.             (*((CharPtr) r+l1+2) == 'c') && (*((CharPtr) r+l1+9) == '\n')) {
  883.             return true;
  884.         }
  885.     }
  886.     return false;
  887. }
  888.  
  889. /***********************************************************************
  890.  *
  891.  * FUNCTION:    ApptGetAppointments
  892.  *
  893.  * DESCRIPTION: This routine returns a list of appointments that are in 
  894.  *              the range of dates specified
  895.  *
  896.  * PARAMETERS:  dbP       - pointer to the database
  897.  *              date      - start date to search from
  898.  *              days      - number a days in search range
  899.  *              apptLists - returned: array of handle of the 
  900.  *                                     appointment list (ApptInfoType)
  901.  *              counts    - returned: returned: array of counts of the 
  902.  *                          number of appointments in each list.
  903.  *
  904.  * RETURNED:    total number of appointments found
  905.  *
  906.  * REVISION HISTORY:
  907.  *            Name    Date        Description
  908.  *            ----    ----        -----------
  909.  *            art    4/7/96    Initial Revision
  910.  *
  911.  ***********************************************************************/
  912. UInt ApptGetAppointments(DmOpenRef dbP, DateType date, Word days,
  913.     VoidHand apptLists [], UInt counts [])
  914. {
  915.     ApptInfoPtr apptList;
  916.     LineItemPtr LineItem;
  917.     LineItemType tli;
  918.     UInt startDate;
  919.     UInt endDate;
  920.     UInt index, i;
  921.     UInt recordNum;
  922.     UInt totalcount=0;
  923.     ULong dateInDays;
  924.     DateType eventDate;
  925.     TimeType startTime;
  926.     TimeType endTime;
  927.     DateType tempDate;
  928.     DateType repeatDate;
  929.     Boolean repeats, dbk3done;
  930.     VoidHand recordH;
  931.     ApptDBRecordType apptRec;
  932.     ApptPackedDBRecordPtr r;
  933.  
  934.     MemSet(apptLists, days * sizeof(VoidHand), 0);
  935.     MemSet(counts, days * sizeof(UInt), 0);
  936.  
  937.     startDate = DateToInt(date);
  938.     tempDate = date;
  939.     DateAdjust(&tempDate, days-1);
  940.     endDate = DateToInt(tempDate);
  941.  
  942.     /* Find the first non-repeating appointment of the day. */
  943.     ApptFindFirst(dbP, date, &recordNum);
  944.     while (true) {
  945.         recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
  946.         if (! recordH) break;
  947.  
  948.         /*
  949.          * Check if the appointment is on the date passed, if it is 
  950.          * add it to the appointment list.
  951.          */
  952.         r = MemHandleLock(recordH);
  953.         startTime = r->when.startTime;
  954.         endTime = r->when.endTime;
  955.         eventDate = r->when.date;
  956.         /* Check if the datebk3 "done" flag is set */
  957.         dbk3done = (PrefsR->ApptPrefs.showdone == '0') && Datebk3Done(r);
  958.         MemHandleUnlock(recordH);
  959.         
  960.         if ((DateToInt(eventDate) < startDate) || 
  961.              (DateToInt(eventDate) > endDate)) break;
  962.  
  963.         if (!dbk3done) {
  964.             /* Add the record to the appointment list. */
  965.             index = DateToDays(eventDate) - DateToDays(date);
  966.  
  967.             if (AddAppointmentToList(&apptLists[index], 0, counts[index], 0,
  968.                     startTime, endTime, eventDate, recordNum, 0)) {
  969.                 counts[index]++;
  970.                 totalcount++;
  971.             } else break;
  972.         }
  973.  
  974.         recordNum++;
  975.     }
  976.  
  977.     /*
  978.      * Add the repeating appointments to the list.  Repeating appointments
  979.      * are stored at the beginning of the database.
  980.      */
  981.     recordNum = 0;
  982.     dateInDays = DateToDays(date);
  983.     while (true) {
  984.         recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
  985.         if (! recordH) break;
  986.         
  987.         r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
  988.         repeats = (r->flags.repeat != 0);
  989.         
  990.         if (repeats) {
  991.             ApptUnpack(r, &apptRec);
  992.  
  993.             if (days == 1) {
  994.  
  995.                 if (ApptRepeatsOnDate(&apptRec, date)) {
  996.                     /* Check if the datebk3 "done" flag is set */
  997.                     dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
  998.                         Datebk3Done(r);
  999.                     if (!dbk3done && AddAppointmentToList(apptLists, 0,
  1000.                         *counts, 0, r->when.startTime, r->when.endTime,
  1001.                         date, recordNum, 0)) {
  1002.                         (*counts)++;
  1003.                         totalcount++;
  1004.                     }
  1005.                 }
  1006.             } else {
  1007.                 repeatDate = date;
  1008.                 while (ApptNextRepeat (&apptRec, &repeatDate)) {
  1009.                     if (DateToInt(repeatDate) > endDate) break;
  1010.                     /* Add the record to the appointment list. */
  1011.                     index = DateToDays(repeatDate) - dateInDays;
  1012.                     /* Check if the datebk3 "done" flag is set */
  1013.                     dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
  1014.                         Datebk3Done(r);
  1015.                     if (!dbk3done && AddAppointmentToList(&apptLists[index], 0,
  1016.                         counts[index], 0, r->when.startTime, r->when.endTime,
  1017.                         repeatDate, recordNum, 0)) {
  1018.                         counts[index]++;
  1019.                         totalcount++;
  1020.                     } else break;
  1021.  
  1022.                     if (DateToInt(repeatDate) == endDate) break;
  1023.  
  1024.                     DateAdjust(&repeatDate, 1);
  1025.                 }
  1026.             }
  1027.         }
  1028.  
  1029.         MemHandleUnlock(recordH);
  1030.  
  1031.         /*
  1032.          * If the record has no repeating info we've reached the end of the 
  1033.          * repeating appointments.
  1034.          */
  1035.         if (! repeats) break;
  1036.         
  1037.         recordNum++;
  1038.     }
  1039.  
  1040.     /* Sort the list by start time. */
  1041.     for (index=0; index < days; index ++) {
  1042.         if (apptLists[index]) {
  1043.             /*
  1044.              * Get the pointer again.  Yeah I know this sucks having to
  1045.              * deref the handle again but I didn't want to have to carry
  1046.              * around a pointer.
  1047.              */
  1048.             MemHandleUnlock(apptLists[index]);
  1049.             apptList = MemHandleLock(apptLists[index]);
  1050.             if (counts[index] >= 2) {
  1051.                 SysInsertionSort(apptList, counts[index], sizeof(ApptInfoType), 
  1052.                     (_comparF *) ApptListCompare, 0L);
  1053.             }
  1054.             /*
  1055.              * Shrink the array down to a more compact structure because we
  1056.              * don't need many of the structure elements anymore.
  1057.              */
  1058.             LineItem = (LineItemPtr) apptList;
  1059.             for (i=0; i < counts[index]; i++) {
  1060.                 /*
  1061.                  * Both LineItem & apptList point to the same spot so make
  1062.                  * sure to use a temporary holding place when copying.
  1063.                  */
  1064.                 tli.recordNum = apptList[i].recordNum;
  1065.                 tli.startTime = apptList[i].startTime;
  1066.                 tli.date = apptList[i].date;
  1067.                 LineItem[i] = tli;
  1068.             }
  1069.             MemHandleUnlock(apptLists[index]);
  1070.             MemHandleResize(apptLists[index], counts[index] *
  1071.                 sizeof(LineItemType));
  1072.         }
  1073.     }
  1074.     return totalcount;
  1075. }
  1076.  
  1077. /***********************************************************************
  1078.  *
  1079.  * FUNCTION:    ApptGetAlarmTime
  1080.  *
  1081.  * DESCRIPTION: This routine determines the date and time of the next alarm
  1082.  *              for the appointment passed.
  1083.  *
  1084.  * PARAMETERS:  apptRec     - pointer to an appointment record
  1085.  *              currentTime - current date and time in seconds
  1086.  *
  1087.  * RETURNED:    date and time of the alarm, in seconds, or zero if there
  1088.  *              is no alarm
  1089.  *
  1090.  * REVISION HISTORY:
  1091.  *            Name    Date        Description
  1092.  *            ----    ----        -----------
  1093.  *            art    6/20/95        Initial Revision
  1094.  *
  1095.  * 8/9/99 - I am changing this to always return the alarm time even if it
  1096.  *          has already passed.  It was originally written to return 0 if
  1097.  *          it was a timed event, and a repeating event would skip to the
  1098.  *          next repeat who's alarm advance was still pending.  Since I need
  1099.  *          to know when we are amidst the alarm advance I always want to
  1100.  *          return the next alarm that hasn't happened regardless of the
  1101.  *          alarm advance.  Actually it still returns 0 if the "end on"
  1102.  *          date has passed.
  1103.  *
  1104.  ***********************************************************************/
  1105. ULong ApptGetAlarmTime(ApptDBRecordPtr apptRec, ULong currentTime,
  1106.     DateTimeType *actualdt)
  1107. {
  1108.     ULong advance;
  1109.     ULong alarmTime;
  1110.     DateType repeatDate;
  1111.     DateTimeType curDateTime;
  1112.     DateTimeType apptDateTime;
  1113.  
  1114.     /* Non-repeating appointment? */
  1115.     if (! apptRec->repeat) {
  1116.         /* An alarm on an untimed event triggers at midnight. */
  1117.         if (TimeToInt(apptRec->when->startTime) == apptNoTime) {
  1118.             apptDateTime.minute = 0;
  1119.             apptDateTime.hour = 0;
  1120.         } else {
  1121.             apptDateTime.minute = apptRec->when->startTime.minutes;
  1122.             apptDateTime.hour = apptRec->when->startTime.hours;
  1123.         }
  1124.         apptDateTime.second = 0;
  1125.         apptDateTime.day = apptRec->when->date.day;
  1126.         apptDateTime.month = apptRec->when->date.month;
  1127.         apptDateTime.year = apptRec->when->date.year + firstYear;
  1128.  
  1129.         /*
  1130.          * Compute the time of the alarm by adjusting the date and time 
  1131.          * of the appointment by the length of the advance notice.
  1132.          */
  1133.         advance = apptRec->alarm->advance;
  1134.         switch (apptRec->alarm->advanceUnit) {
  1135.         case aauMinutes:
  1136.             advance *= minutesInSeconds;
  1137.             break;
  1138.         case aauHours:
  1139.             advance *= hoursInSeconds;
  1140.             break;
  1141.         case aauDays:
  1142.             advance *= daysInSeconds;
  1143.             break;
  1144.         }
  1145.  
  1146.         alarmTime = TimDateTimeToSeconds(&apptDateTime) - advance;
  1147.         /* always return these even if the alarm advance has passed */
  1148.         *actualdt = apptDateTime;
  1149.         return (alarmTime);
  1150.     }
  1151.  
  1152.     /* Repeating appointment. */
  1153.     TimSecondsToDateTime(currentTime, &curDateTime);
  1154.     repeatDate.year = curDateTime.year - firstYear;
  1155.     repeatDate.month = curDateTime.month;
  1156.     repeatDate.day = curDateTime.day;
  1157.     
  1158.     while (ApptNextRepeat(apptRec, &repeatDate)) {
  1159.         /* An alarm on an untimed event triggers at midnight. */
  1160.         if (TimeToInt(apptRec->when->startTime) == apptNoTime) {
  1161.             apptDateTime.minute = 0;
  1162.             apptDateTime.hour = 0;
  1163.         } else {
  1164.             apptDateTime.minute = apptRec->when->startTime.minutes;
  1165.             apptDateTime.hour = apptRec->when->startTime.hours;
  1166.         }
  1167.         apptDateTime.second = 0;
  1168.         apptDateTime.day = repeatDate.day;
  1169.         apptDateTime.month = repeatDate.month;
  1170.         apptDateTime.year = repeatDate.year + firstYear;
  1171.  
  1172.         /*
  1173.          * Compute the time of the alarm by adjusting the date and time 
  1174.          * of the appointment by the length of the advance notice.
  1175.          */
  1176.         advance = apptRec->alarm->advance;
  1177.         switch (apptRec->alarm->advanceUnit) {
  1178.         case aauMinutes:
  1179.             advance *= minutesInSeconds;
  1180.             break;
  1181.         case aauHours:
  1182.             advance *= hoursInSeconds;
  1183.             break;
  1184.         case aauDays:
  1185.             advance *= daysInSeconds;
  1186.             break;
  1187.         }
  1188.  
  1189.         alarmTime = TimDateTimeToSeconds(&apptDateTime) - advance;
  1190.         /*
  1191.          * Always return these even if the alarm advance has passed.  It was
  1192.          * originally written to go to the next repeat if the alarm advance
  1193.          * has already passed.  I don't want that because I have a preference
  1194.          * option that lets me show these events while they are between the
  1195.          * alarm advance and the event.
  1196.          */
  1197.         *actualdt = apptDateTime;
  1198.         return (alarmTime);
  1199.         
  1200.     } 
  1201.         
  1202.     /* the event has an "end on" date that has passed */
  1203.     return (0);
  1204. }
  1205.  
  1206. /*
  1207.  * This makes a list of the silent alarms that went off today for untimed
  1208.  * events.  There is also a preference that lets you choose to have the item
  1209.  * appear each day until the event.
  1210.  */
  1211. UInt ApptGetUntimedAlarms(DmOpenRef dbP, DateType date, Word days,
  1212.     VoidHand apptLists [], UInt counts [])
  1213. {
  1214.     ApptInfoPtr apptList;
  1215.     LineItemPtr LineItem;
  1216.     LineItemType tli;
  1217.     UInt i;
  1218.     UInt totalcount=0;
  1219.     UInt origcount;
  1220.     ULong shrunk;
  1221.     DateTimeType alarmdt, nextdt, actualdt;
  1222.     TimeType notime={0, 0};
  1223.     DateType nodate={0, 0}, actuald, nextd;
  1224.     ULong alarmTime;
  1225.     UInt recordNum;
  1226.     UInt numRecords;
  1227.     ULong next;
  1228.     VoidHand recordH;
  1229.     ApptDBRecordType apptRec;
  1230.     ApptPackedDBRecordPtr r;
  1231.     UInt startDate;
  1232.     UInt endDate;
  1233.     DateType tempDate;
  1234.     Boolean dbk3done;
  1235.     Word daysm1=days-1;
  1236.  
  1237.     /*
  1238.      * Get the seconds that represents 00:00 of the current day.
  1239.      */
  1240.     alarmdt.month = date.month;
  1241.     alarmdt.day = date.day;
  1242.     alarmdt.year = date.year+1904;
  1243.     alarmdt.hour = 0;
  1244.     alarmdt.minute = 0;
  1245.     alarmdt.second = 0;
  1246.     alarmTime = TimDateTimeToSeconds(&alarmdt);
  1247.  
  1248.     /*
  1249.      * Our span of "reasonable" appointments to check for are between today
  1250.      * and 14 days from now.  No reasonable person would set an untimed alarm
  1251.      * in excess of that.
  1252.      */
  1253.     startDate = DateToInt(date);
  1254.     tempDate = date;
  1255.     DateAdjust(&tempDate, 13);
  1256.     endDate = DateToInt(tempDate);
  1257.  
  1258.     /*
  1259.      * Keep in mind that AddAppointmentToList() is expecting the list to
  1260.      * be locked already if it exists.  So we must lock it down.  Don't worry
  1261.      * about saving the pointer because it does a MemDeref().  In addition,
  1262.      * the list was most likely trimmed and needs to be padded to multiples
  1263.      * of 10 items.  But that's taken care of in the function.
  1264.      */
  1265.     if (apptLists[daysm1]) {
  1266.         MemHandleLock(apptLists[daysm1]);
  1267.         /*
  1268.          * There are probably some packed LineItemType elements already
  1269.          * present since the handle exists.  We want to skip over them and
  1270.          * add ApptTypeInfo structures, then sort, and then shrink the new
  1271.          * elements down to LineItemType as well.
  1272.          */
  1273.         shrunk = counts[daysm1]*sizeof(LineItemType);
  1274.     } else shrunk = 0;
  1275.  
  1276.     origcount = counts[daysm1]; /* for the sorting at the end */
  1277.  
  1278.     /* Search the database for appointments with alarms at the passed time. */
  1279.     numRecords = DmNumRecords(dbP);
  1280.  
  1281.     /*
  1282.      * We'll start searching for untimed alarms from today.  This is the
  1283.      * loop for non-repeats.
  1284.      */
  1285.     ApptFindFirst(dbP, date, &recordNum);
  1286.  
  1287.     for (; recordNum < numRecords; recordNum++) {
  1288.         recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
  1289.         if (! recordH) break;
  1290.         
  1291.         r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
  1292.         
  1293.         if (r->flags.alarm) {
  1294.             ApptUnpack(r, &apptRec);
  1295.  
  1296.             if ((DateToInt(apptRec.when->date) < startDate) || 
  1297.                  (DateToInt(apptRec.when->date) > endDate)) {
  1298.                 MemHandleUnlock(recordH);
  1299.                 break;
  1300.             }
  1301.  
  1302.             if (TimeToInt(apptRec.when->startTime) == apptNoTime) {
  1303.                 next = ApptGetAlarmTime(&apptRec, alarmTime, &actualdt);
  1304.                 TimSecondsToDateTime(next, &nextdt);
  1305.                 nextd.month = nextdt.month;
  1306.                 nextd.day = nextdt.day;
  1307.                 nextd.year = nextdt.year-1904;
  1308.                 actuald.month = actualdt.month;
  1309.                 actuald.day = actualdt.day;
  1310.                 actuald.year = actualdt.year-1904;
  1311.                 /*
  1312.                  * Check if it lands on the current day.  But also we don't
  1313.                  * want to show it if it has a 0 advance (day == day).
  1314.                  *
  1315.                  * But also we want to keep an item on the list if the user
  1316.                  * selects the preference.
  1317.                  */
  1318.                 if (next &&
  1319.                     (DateToInt(date) != DateToInt(actuald)) &&
  1320.                     ((DateToInt(nextd) == DateToInt(date)) ||
  1321.                     ((PrefsR->ToDoPrefs.showua == '1') &&
  1322.                     (DateToInt(nextd) <= DateToInt(date))))) {
  1323.  
  1324.                     /* Check if the datebk3 "done" flag is set */
  1325.                     dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
  1326.                         Datebk3Done(r);
  1327.                     if (!dbk3done) {
  1328.                         /*
  1329.                          * Add a title for the untimed listings.  This is drawn
  1330.                          * in the table callback.
  1331.                          */
  1332.                         if ((totalcount == 0) &&
  1333.                             (AddAppointmentToList(&apptLists[daysm1], shrunk,
  1334.                                 counts[daysm1]-origcount, origcount, notime,
  1335.                                 notime, nodate, 0, 3))) {
  1336.                             /*
  1337.                              * apptAlarmLine is the line number where the
  1338.                              * Untimed Alarms start.  It is linear from the
  1339.                              * start of the list so we need to add up the
  1340.                              * previous counts.
  1341.                              */
  1342.                             apptAlarmLine = 0;
  1343.                             for (i=0; i < days; i++) apptAlarmLine += counts[i];
  1344.                             counts[daysm1]++;
  1345.                             totalcount++;
  1346.                         }
  1347.                         /* Add the record to the alarm list. */
  1348.                         if (AddAppointmentToList(&apptLists[daysm1], shrunk,
  1349.                             counts[daysm1]-origcount, origcount,
  1350.                             apptRec.when->startTime,
  1351.                             apptRec.when->endTime, actuald, recordNum, 0)) {
  1352.                             counts[daysm1]++;
  1353.                             totalcount++;
  1354.                         }
  1355.                     }
  1356.                 }
  1357.             }
  1358.  
  1359.         }
  1360.         MemHandleUnlock(recordH);
  1361.     }
  1362.  
  1363.     /*
  1364.      * Now we do the repeat events which are at the beginning of the database.
  1365.      */
  1366.  
  1367.     for (recordNum=0; recordNum < numRecords; recordNum++) {
  1368.         recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
  1369.         if (! recordH) break;
  1370.         
  1371.         r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
  1372.         
  1373.         /* stop when there are no more repeat records */
  1374.         if (!r->flags.repeat) {
  1375.             MemHandleUnlock(recordH);
  1376.             break;
  1377.         }
  1378.  
  1379.         if (r->flags.alarm) {
  1380.             ApptUnpack(r, &apptRec);
  1381.  
  1382.             if (TimeToInt(apptRec.when->startTime) == apptNoTime) {
  1383.                 next = ApptGetAlarmTime(&apptRec, alarmTime, &actualdt);
  1384.                 TimSecondsToDateTime(next, &nextdt);
  1385.                 nextd.month = nextdt.month;
  1386.                 nextd.day = nextdt.day;
  1387.                 nextd.year = nextdt.year-1904;
  1388.                 actuald.month = actualdt.month;
  1389.                 actuald.day = actualdt.day;
  1390.                 actuald.year = actualdt.year-1904;
  1391.                 if (next &&
  1392.                     (DateToInt(actuald) >= startDate) &&
  1393.                     (DateToInt(actuald) <= endDate) &&
  1394.                     (DateToInt(date) != DateToInt(actuald)) &&
  1395.                     ((DateToInt(nextd) == DateToInt(date)) ||
  1396.                     ((PrefsR->ToDoPrefs.showua == '1') &&
  1397.                     (DateToInt(nextd) <= DateToInt(date))))) {
  1398.                     /* Check if the datebk3 "done" flag is set */
  1399.                     dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
  1400.                         Datebk3Done(r);
  1401.                     if (!dbk3done) {
  1402.                         /*
  1403.                          * Add a title for the untimed listings.  This is drawn
  1404.                          * in the table callback.
  1405.                          */
  1406.                         if ((totalcount == 0) &&
  1407.                             (AddAppointmentToList(&apptLists[daysm1], shrunk,
  1408.                                 counts[daysm1]-origcount, origcount, notime,
  1409.                                 notime, nodate, 0, 3))) {
  1410.                             /*
  1411.                              * apptAlarmLine is the line number where the
  1412.                              * Untimed Alarms start.  It is linear from the
  1413.                              * start of the list so we need to add up the
  1414.                              * previous counts.
  1415.                              */
  1416.                             apptAlarmLine = 0;
  1417.                             for (i=0; i < days; i++) apptAlarmLine += counts[i];
  1418.                             counts[daysm1]++;
  1419.                             totalcount++;
  1420.                         }
  1421.                         /* Add the record to the alarm list. */
  1422.                         if (AddAppointmentToList(&apptLists[daysm1], shrunk,
  1423.                             counts[daysm1]-origcount, origcount,
  1424.                             apptRec.when->startTime,
  1425.                             apptRec.when->endTime, actuald, recordNum, 0)) {
  1426.                             counts[daysm1]++;
  1427.                             totalcount++;
  1428.                         }
  1429.                     }
  1430.                 }
  1431.             }
  1432.         }
  1433.  
  1434.         MemHandleUnlock(recordH);
  1435.     }
  1436.  
  1437.     /*
  1438.      * This was locked inside AddAppointmentToList, and it is also buffered
  1439.      * to 10 positions and must be trimmed.  Also we want to sort the end
  1440.      * of the list.
  1441.      */
  1442.     if (apptLists[daysm1]) {
  1443.         if (counts[daysm1] > origcount) {
  1444.             /*
  1445.              * Get the pointer again.  Yeah I know this sucks having to
  1446.              * deref the handle again but I didn't want to have to carry
  1447.              * around a pointer.
  1448.              */
  1449.             MemHandleUnlock(apptLists[daysm1]);
  1450.             apptList = MemHandleLock(apptLists[daysm1]);
  1451.             apptList = (ApptInfoPtr) (((char *) apptList)+shrunk);
  1452.             if ((counts[daysm1]-origcount-1) >= 2) {
  1453.                 SysInsertionSort(&apptList[1], counts[daysm1]-origcount-1,
  1454.                     sizeof(ApptInfoType), (_comparF *) ApptListCompare, 0L);
  1455.             }
  1456.             /*
  1457.              * Shrink the array down to a more compact structure because we
  1458.              * don't need many of the structure elements anymore.
  1459.              */
  1460.             LineItem = (LineItemPtr) apptList;
  1461.             for (i=0; i < (counts[daysm1]-origcount); i++) {
  1462.                 /*
  1463.                  * Both LineItem & apptList point to the same spot so make
  1464.                  * sure to use a temporary holding place when copying.
  1465.                  */
  1466.                 tli.recordNum = apptList[i].recordNum;
  1467.                 tli.startTime = apptList[i].startTime;
  1468.                 tli.date = apptList[i].date;
  1469.                 LineItem[i] = tli;
  1470.             }
  1471.             MemHandleUnlock(apptLists[daysm1]);
  1472.             MemHandleResize(apptLists[daysm1],
  1473.                 counts[daysm1]*sizeof(LineItemType));
  1474.         } else MemHandleUnlock(apptLists[daysm1]);
  1475.     }
  1476.  
  1477.     return totalcount;
  1478. }
  1479.